/*
 ============================================================================
 Name        pfs: pfsFuse.c
 Author      : superacidos
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */

#define FUSE_USE_VERSION 26

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "config.h"
#include "fat.h"
#include "fat_defs.h"
#include "archivo.h"

extern struct stConfig config;


static int32_t pfs_truncate(const char *path, off_t size)
{
	path++;
	int32_t clusterInicial;
	int32_t res;
	int32_t numCluster = obtenerClusterInicial(path);
	if(numCluster!= 0)
	{
		res = truncarArchivo(path, size, &clusterInicial);
		if(res != 1)//Si es 1 es que no pudo truncar
		{

			//SI ESTA EN CACHE, LE ACTUALIZO EL TAMAÑO
			ARCHIVO *arch =buscarNodoArchivoXCluster(numCluster);
			if(arch != NULL)
			{
				arch->size = size;
			}

		}
	}
	else
	{
		res = 1;
	}

	return res;
}

static int32_t pfs_rename(const char *pathViejo, const char *newName)
{
	printf("Viejo %s, Nuevo %s", pathViejo, newName);

	pathViejo++;
	newName++;
	int32_t res = renombrarArchivo(pathViejo, newName);

	//SI ESTA EN CACHE, LE ACTUALIZO EL PATH
	int32_t numCluster = obtenerClusterInicial(newName);
	if(numCluster != 0)
	{
		ARCHIVO *arch =buscarNodoArchivoXCluster(numCluster);
		if(arch != NULL)
		{
			memcpy(arch->path, newName, strlen(newName));
		}
	}
	else
		res = 1;

	return res;
}

static ssize_t pfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
	//buf es donde guardar el contenido
	//size es la cantidad a leer
	//offset es a partir de donde leer
	//fi no lo uso por ahora
	//devuelve la cantidad leida

	size_t len;
	(void) fi;

	//memset(buf,'\0',sizeof(buf));
	path++;
	ARCHIVO *arch = buscarNodoArchivoXFH(fi->fh);

	if(offset + size < arch->size)
	{
		memset(buf,'\0',size);
	}
	else
	{
		memset(buf,'\0',arch->size - offset);
	}

	int32_t numCluser = arch->startcluster;
	if(numCluser != 0)
	{
		if(arch->size != 0 && offset < arch->size)
		{
			if (offset + size > arch->size)
			{
				size = arch->size - offset;
			}

			char *texto = leerArchivo(arch, size, offset);
			memcpy(buf, texto, size);
			free(texto);

			return size;

		}
		else
			return 0;
	}
	else
		return 1;

}

static int32_t pfs_open(const char *path, struct fuse_file_info *fi)
{
	//Chequear que existe el archivo
	//CHEQUEAR SI LA OPERACION ES PERMITIDA SEGUN LOS FLAGS
	//Incializar en la lista de archivos con su cache

	//SEGUN ENTIENDO ESTO SOLO CREA LA CACHE DE ARCHIVO


	//if((fi->flags & 3) != O_RDONLY)
	//  return -EACCES;

	ARCHIVO *arch;
	int32_t numeroBytesEntradaCluster;

	//Cargo nuevo archivo
	if(path[0] ==  '/')
	{
		path++;
	}


	int32_t numCluster = obtenerClusterInicial(path);

	arch = buscarNodoArchivoXCluster(numCluster);
	if(arch == NULL)
	{
		int32_t cantClusters = 0;
		arch  = creaNodoArchivo();
		arch->startcluster = numCluster;
		arch->flags = fi->flags;
		arch->aperturas_simultaneas = 1;
		if(arch->startcluster != 0 && arch->startcluster != 2)
		{
			arch -> listaClusters = listaClustersEncadenados(arch->startcluster, &cantClusters);
		}
		int32_t clusterPadre, byteEntrada;
		struct fat_dir_entry *directoryEntry  = obtenerEntradaDataRegion(path, &clusterPadre, &byteEntrada);
		arch->parentcluster = clusterPadre;
		arch->bytenum = byteEntrada;
		arch->size = directoryEntry->FileSize;
		free(directoryEntry);
		memcpy(arch->path, path, strlen(path));
		fi->fh = arch->fh;

		//Inicio su cache si esta habilitada
		if(config.tamanioCache != 0)
			cache_archivo_crear(arch);
	}
	else
	{
		fi->fh = arch->fh;
		(arch->aperturas_simultaneas)++;
	}

	//Escribo en el log los clusters que contienen al archivo
	if(config.loguear==1)
	{
		if(numCluster != 0 && numCluster != 2)
		{
			int pid = getpid();
			logCadenaClusters(arch->startcluster,pid);
		}

	}

	return 0;

}

static int32_t pfs_create(const char *path, mode_t modo, struct fuse_file_info *fi)
{
	//TODO AVERIGUAR SI SE PUEDE CREAR CON DIFERENTES MODOS,
	//      Y VER COMO USAMOS EL FI

	//Chequear que existe el directorio
	//Crea Archivo
	//Abre archivo (incializar en la lista de archivos con su cache)

	int32_t res = 0;

	//Cargo nuevo archivo
	ARCHIVO *arch;
	path++;
	crearArchivo(path);


	//ABRO EL ARCHIVO
	res = pfs_open(path, fi);

	return res;
}

static int32_t pfs_getattr(const char *path, struct stat *stbuf)
{
	//funcion que se ejecuta cuando me piden los atributos de
	//un directorio/archivo
	//devuelve 0 si fue exitoso o El numero de Error (Creo)
	//path es el dir/archivo que quiere los stats ('/','/unArchivo','/Dir1/Archivo.txt',etc)
	//stbuf es la estructura en la que tengo que devolver los stats
	int32_t res = 0, cluster, bytes;

	memset(stbuf, 0, sizeof(struct stat));

	stbuf->st_nlink = 1;
	stbuf->st_blksize  = BYTESPERSEC*4;  //TAMAÑO BLOQUE EN BYTES  (DOS SECTORES ES UN BLOQUE)

	//SI NO ES RAIZ
	if(strcmp(path, "/")!=0)
	{
		path++;

		struct fat_dir_entry *entry;
		entry = obtenerEntradaDataRegion(path, &cluster, &bytes);

		if(entry != NULL)
		{
			if(entry->Attr == 0x10)//ES DIRECTORIO
			{
				stbuf->st_mode = S_IFDIR | 0755;
				stbuf->st_size = 0;
				stbuf->st_blocks = 0;

			}
			else if(entry->Attr == 0x20)//ES ARCHIVO
			{
				stbuf->st_mode = S_IFREG | entry->Attr;
				stbuf->st_blocks = 4; //MANEJO TODO CON CLUSTERS.  4 BLOQUES ES UN CLUSTER.
				stbuf->st_size = entry->FileSize;
			}
			else
			{
				free(entry);
				return -ENOENT;
			}
		}
		else
		{
			return -ENOENT;
		}
		free(entry);
		return res;
	}
	else
	{
		stbuf->st_nlink = 1;  //EN TODOS LADOS LO VI = 1
		stbuf->st_mode = S_IFDIR | 0755;
		stbuf->st_nlink = 1;  //EN TODOS LADOS LO VI = 1
		stbuf->st_blksize  = BYTESPERSEC*2;  //TAMAÑO BLOQUE EN BYTES  (DOS SECTORES ES UN BLOQUE)

		return 0;
	}


}

static int32_t pfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
		off_t offset, struct fuse_file_info *fi)
{
	//Funciona!
	//Ojo, Aca no va el path++

	(void) offset;
	(void) fi;

	struct nodoLFN *listado, *aux;
	if(strlen(path)>1)
	{
		path++;
	}

	int32_t numCluster =obtenerClusterInicial(path);
	listado = listarDirectorioDataRegion(numCluster);


	filler(buf, ".", NULL, 0);
	filler(buf, "..", NULL, 0);
	while(listado!=NULL)
	{
		filler(buf,listado->nombre,NULL,0);
		aux = listado;
		listado = listado->sgt;
		free(aux);
	}

	return 0;//salgo
}

static int32_t pfs_write(const char *path, const char *buf, size_t size, off_t offset,
		struct fuse_file_info *fi)
{
	//Llama al modificar archivo
	//devuelve el tamaño de lo que escribio
	ARCHIVO *arch = buscarNodoArchivoXFH(fi->fh);
	if(arch != NULL)
	{
		int32_t res = modificarArchivo(arch, buf, offset, size);
		return res;
	}
	else
	{
		return 1;
	}
}



static int32_t pfs_mkdir(const char * path,mode_t mode)
{
	path++;
	int32_t res = crearDirectorio(path);
	return res;
}

static int32_t pfs_flush(const char * path, struct fuse_file_info * fi)
{
	//ACA FLUSH_CACHE CREO

	ARCHIVO *arch = buscarNodoArchivoXFH(fi->fh);
	if(config.tamanioCache > 0)
		cache_archivo_flush(arch);

	return 0;
}

static int32_t pfs_release(const char * path, struct fuse_file_info * fi)
{
	//BORRA DE LA LISTA DE ARCHIVOS Y BORRA LA CACHE
	ARCHIVO *arch = buscarNodoArchivoXFH(fi->fh);
	quitarNodoArchivo(arch);
	return 0;
}

static int32_t pfs_unlink(const char * path)
{
	//todo chequear si antes del unlink
	path++;
	int32_t res = 0;

	res = borrarArchivo(path);

	return res;
}

static int32_t pfs_rmdir(const char * path)
{
	path++;
	int32_t res = borrarDirectorio(path);
	return res;

}


static struct fuse_operations pfs_oper = {
		.getattr   = pfs_getattr,  //FUNCIONA APARENTEMENTE
		.readdir = pfs_readdir,  //FUNCIONA APARENTEMENTE
		.open   = pfs_open,		//FUNCIONA APARENTEMENTE
		.read   = pfs_read,
		.truncate = pfs_truncate,
		.rename = pfs_rename,	//FUNCIONA APARENTEMENTE
		.write = pfs_write,
		.mkdir = pfs_mkdir, //FUNCIONA APARENTEMENTE
		.create = pfs_create,  //FUNCIONA APARENTEMENTE
		.flush = pfs_flush,		//FUNCIONA ARCHIVO, CACHE VACIA
		.release = pfs_release,	//FUNCIONA ARCHIVO, CACHE VACIA
		.unlink = pfs_unlink,	//FUNCIONA APARENTEMENTE
		.rmdir = pfs_rmdir  //FUNCIONA APARENTEMENTE
};

int32_t fuse_init(int32_t argc, char *argv[])
{
	return fuse_main(argc, argv, &pfs_oper, NULL);
}

